# main_gui.py
import sys
import time

from PyQt6.QtGui import QFont, QPixmap, QPainter
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                             QHBoxLayout, QPushButton, QComboBox, QPlainTextEdit,
                             QGroupBox, QLabel, QGridLayout, QCheckBox, QMessageBox)
from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot, Qt, QTimer

import serial
import serial.tools.list_ports

import protocol_handler
from calibration_window import CalibrationWindow
from inverse_solver_window import InverseSolverWindow
from plot_window import PlotWindow
from style import GLOBAL_STYLE_SHEET


class SerialWorker(QObject):
    data_received = pyqtSignal(bytes)
    error_occurred = pyqtSignal(str)
    connection_lost = pyqtSignal(str)

    def __init__(self, s):
        super().__init__(); self.s, self._run = s, True

    @pyqtSlot()
    def run(self):
        buf = bytearray()
        while self._run:
            try:
                if not self.s.is_open: break
                d = self.s.read(self.s.in_waiting or 1)
                if d:
                    buf.extend(d)
                    while len(buf) >= 5:
                        if buf[0] != 0x0A: buf.pop(0); continue
                        p_len, f_len = buf[2], 1 + 1 + 1 + buf[2] + 2
                        if len(buf) >= f_len:
                            frame, d_part = buf[:f_len], buf[:f_len - 2]
                            crc = protocol_handler.modbus_crc16(d_part)
                            if frame[-2:] == crc:
                                self.data_received.emit(bytes(frame))
                                buf = buf[f_len:]
                            else:
                                self.error_occurred.emit(f"receive CRC{frame[-2:].hex()} error, correct {crc.hex()}"); buf.pop(0)
                        else:
                            break
            except serial.SerialException as e:
                self.connection_lost.emit(str(e))
                self._run = False
            time.sleep(0.01)

    def stop(self):
        self._run = False


class SerialProtocolApp(QMainWindow):
    # 定义信号，用于将数据分发给绘图窗口
    plot_data_ready = pyqtSignal(dict)
    acq_config_received = pyqtSignal(dict)
    pd_voltages_response = pyqtSignal(list)
    connection_status_changed = pyqtSignal(bool)  # 新增：连接状态改变信号 (bool: True=连接, False=断开)
    def __init__(self):
        super().__init__()
        self.setWindowTitle("主控界面")
        self.setGeometry(100, 100, 500, 600)

        self.serial_port = serial.Serial()
        self.serial_thread, self.serial_worker = None, None
        self.DEVICE_ADDRESS = 0x0A
        self.value_labels = {}

        # --- 初始化子窗口并设置双向信号连接 ---
        self.plot_window = PlotWindow(self)
        self.calibration_window = CalibrationWindow(self)
        self.solver_window = InverseSolverWindow(self)
        # 主 -> 子 (数据响应)
        self.plot_data_ready.connect(self.plot_window.update_plot_data)
        self.pd_voltages_response.connect(self.calibration_window.on_voltages_received)
        self.pd_voltages_response.connect(self.solver_window.on_voltages_received)
        # 子 -> 主 (指令请求)
        self.calibration_window.request_pd_voltage_reading.connect(self._on_immediate_reading_request)
        self.solver_window.request_fresh_reading.connect(self._on_immediate_reading_request)

        self.connection_status_changed.connect(self.calibration_window.on_connection_status_changed)
        self.connection_status_changed.connect(self.solver_window.on_connection_status_changed)
        self.pd_read_timer = QTimer(self)
        self.pd_read_timer.timeout.connect(self.auto_send_pd_read_command)
        self.solver_window.start_auto_read_requested.connect(self._on_start_auto_read_request)
        self.solver_window.stop_auto_read_requested.connect(self._on_stop_auto_read_request)

        self._init_ui()
        self.update_port_list()

        # try:
        #     with open("style.qss", "r", encoding="utf-8") as f:
        #         self.setStyleSheet(f.read())
        # except FileNotFoundError:
        #     print("Warning: style.qss not found. Using default style.")

    def _init_ui(self):
        main_widget = QWidget(); self.setCentralWidget(main_widget); main_layout = QVBoxLayout(main_widget)
        conn_groupbox = QGroupBox("串口设置"); conn_layout = QHBoxLayout(); conn_groupbox.setLayout(conn_layout)
        conn_layout.addWidget(QLabel("端口:")); self.port_combobox = QComboBox(); conn_layout.addWidget(self.port_combobox)
        self.refresh_button = QPushButton("刷新", clicked=self.update_port_list); conn_layout.addWidget(self.refresh_button)
        self.connect_button = QPushButton("连接", checkable=True, clicked=self.connect_disconnect); conn_layout.addWidget(self.connect_button)
        conn_layout.addStretch(); main_layout.addWidget(conn_groupbox)
        cmd_groupbox = QGroupBox("功能与控制");
        cmd_layout = QHBoxLayout();
        cmd_groupbox.setLayout(cmd_layout)
        self.toggle_plot_button = QPushButton("数据采集窗口", checkable=True);
        self.toggle_plot_button.toggled.connect(self.plot_window.setVisible);
        self.plot_window.window_closed.connect(lambda: self.toggle_plot_button.setChecked(False))
        cmd_layout.addWidget(self.toggle_plot_button)
        self.toggle_calib_button = QPushButton("打开标定窗口", checkable=True);
        self.toggle_calib_button.toggled.connect(self.calibration_window.setVisible);
        self.calibration_window.window_closed.connect(lambda: self.toggle_calib_button.setChecked(False))
        cmd_layout.addWidget(self.toggle_calib_button)
        self.toggle_solver_button = QPushButton("打开反解窗口", checkable=True)
        self.toggle_solver_button.toggled.connect(self.solver_window.setVisible)
        self.solver_window.window_closed.connect(lambda: self.toggle_solver_button.setChecked(False))
        cmd_layout.addWidget(self.toggle_solver_button)

        main_layout.addWidget(cmd_groupbox)
        value_groupbox = QGroupBox("实时数值 (PD电压)"); self.value_layout = QGridLayout(); value_groupbox.setLayout(self.value_layout)
        header_ch = QLabel("<b>通道</b>"); header_raw = QLabel("<b>原始值 (V)</b>"); header_filtered = QLabel("<b>滤波后 (V)</b>")
        header_ch.setAlignment(Qt.AlignmentFlag.AlignCenter); header_raw.setAlignment(Qt.AlignmentFlag.AlignCenter); header_filtered.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.value_layout.addWidget(header_ch, 0, 0); self.value_layout.addWidget(header_raw, 0, 1); self.value_layout.addWidget(header_filtered, 0, 2)
        for i in range(12):
            self.value_layout.addWidget(QLabel(f"CH {i+1:02d}:"), i + 1, 0)
            raw_label = QLabel("N/A"); filtered_label = QLabel("N/A")
            raw_label.setAlignment(Qt.AlignmentFlag.AlignCenter); filtered_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
            self.value_layout.addWidget(raw_label, i + 1, 1); self.value_layout.addWidget(filtered_label, i + 1, 2)
            self.value_labels[i] = {'raw': raw_label, 'filtered': filtered_label}
        main_layout.addWidget(value_groupbox)
        auto_read_groupbox = QGroupBox("自动读取PD电压"); auto_read_layout = QHBoxLayout(); auto_read_groupbox.setLayout(auto_read_layout)
        self.auto_read_pd_checkbox = QCheckBox("启动自动读取 (每500ms)"); self.auto_read_pd_checkbox.stateChanged.connect(self.toggle_auto_read_pd)
        auto_read_layout.addWidget(self.auto_read_pd_checkbox); auto_read_layout.addWidget(QPushButton("手动读取一次", clicked=lambda: self.send_command(b'caz')))
        main_layout.addWidget(auto_read_groupbox)
        log_groupbox = QGroupBox("数据日志"); log_layout = QVBoxLayout(); log_groupbox.setLayout(log_layout)
        self.log_text_edit = QPlainTextEdit(); self.log_text_edit.setReadOnly(True); log_layout.addWidget(self.log_text_edit)
        main_layout.addWidget(log_groupbox, 1)

    @pyqtSlot()
    def _on_stop_auto_read_request(self):
        """当反解窗口停止监控时，自动取消勾选并停止主窗口的定时读取功能。"""
        if self.auto_read_pd_checkbox.isChecked():
            self.log_message("由反解窗口触发，自动停止定时读取。", "purple")
            self.auto_read_pd_checkbox.setChecked(False)

    # <--- NEW: Slot to handle critical connection loss ---
    @pyqtSlot(str)
    def _on_connection_lost(self, error_msg):
        """处理串口意外断开的事件"""
        self.log_message(f"严重错误: 串口连接丢失！ {error_msg}", "red")

        # Show a pop-up alert to the user
        QMessageBox.critical(self, "连接丢失", f"串口连接已意外断开。\n\n错误: {error_msg}\n\n请检查设备连接。")

        self.connect_disconnect(False)
        self.connect_button.setChecked(False)

    @pyqtSlot()
    def _on_start_auto_read_request(self):
        """当反解窗口开始监控时，自动勾选并启动主窗口的定时读取功能。"""
        if not self.auto_read_pd_checkbox.isChecked():
            self.log_message("由反解窗口触发，自动启动定时读取...", "purple")
            self.auto_read_pd_checkbox.setChecked(True)

    @pyqtSlot()
    def _on_immediate_reading_request(self):
        """槽函数：响应来自任何子窗口的即时采样请求"""
        sender_name = "未知窗口"
        if self.sender() == self.calibration_window:
            sender_name = "标定窗口"
        elif self.sender() == self.solver_window:
            sender_name = "反解窗口"

        self.log_message(f"收到来自 {sender_name} 的采样请求，发送'caz'指令...", "purple")
        self.send_command(b'caz')

    @pyqtSlot(bool)
    def toggle_plot_window(self, checked):
        if checked:
            self.plot_window.show()
        else:
            self.plot_window.hide()

    def reset_ui_state(self):
        self.plot_window.reset()
        for i in range(12):
            if i in self.value_labels:
                self.value_labels[i]['raw'].setText("N/A");
                self.value_labels[i]['filtered'].setText("N/A")
        self.log_message("UI状态已重置", "purple")

    @pyqtSlot(bytes)
    def handle_received_frame(self, frame: bytes):
        log_string, parsed_data = protocol_handler.parse_frame(frame)
        data_type = parsed_data.get('type')

        # --- 数据分发中心 ---
        if data_type == 'plot_data':
            self.plot_data_ready.emit(parsed_data)
        elif data_type == 'acq_config':
            self.acq_config_received.emit(parsed_data)
        elif data_type == 'pd_voltage':
            if log_string: self.log_message(log_string, "darkgreen")
            self._update_pd_voltage_ui(parsed_data.get('voltages', []))
        elif data_type == 'ack_write_config':
            # 对于写入配置的ack，只在主窗口打印日志
            if log_string: self.log_message(log_string, "darkgreen")
        elif log_string:
            color = "red" if data_type == 'error' else "darkgreen"
            self.log_message(log_string, color)

    @pyqtSlot(int)
    def toggle_auto_read_pd(self, state):
        if state == Qt.CheckState.Checked.value:
            if self.serial_port.is_open:
                self.pd_read_timer.start(500)
                self.log_message("自动读取PD电压已启动", "purple")
            else:
                self.log_message("错误: 请先连接", "red");
                self.auto_read_pd_checkbox.setChecked(False)
        else:
            self.pd_read_timer.stop();
            self.log_message("自动读取PD电压已停止", "purple")
    def _update_pd_voltage_ui(self, v):
        if v and len(v) >= 24:
            for i in range(12):
                if i in self.value_labels:
                    self.value_labels[i]['raw'].setText(f"{v[i]:.4f}")
                    self.value_labels[i]['filtered'].setText(f"{v[i + 12]:.4f}")
        # 2. 将滤波后的数据作为“响应”发射出去
        filtered_voltages = v[12:]
        self.pd_voltages_response.emit(filtered_voltages)


    @pyqtSlot()
    def auto_send_pd_read_command(self):
        if self.serial_port.is_open:
            self.send_command(b'caz')
        else:
            self.pd_read_timer.stop();
            self.auto_read_pd_checkbox.setChecked(False)
            self.log_message("错误: 串口断开，自动读取已停止", "red")

    def closeEvent(self, event):
        """
        Overrides the default close event to add a confirmation dialog
        and ensure all child windows are closed properly.
        """
        # Create the confirmation dialog
        reply = QMessageBox.question(
            self,
            '退出确认',  # Dialog title
            '您确定要退出程序吗？',  # Dialog message
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,  # Buttons
            QMessageBox.StandardButton.No  # Default button
        )

        if reply == QMessageBox.StandardButton.Yes:
            self.plot_window.close()
            self.calibration_window.close()
            self.solver_window.close()

            if self.serial_port.is_open:
                self.connect_disconnect(False)
            event.accept()
        else:
            event.ignore()

    def update_port_list(self):
        self.port_combobox.clear();
        self.port_combobox.addItems([p.device for p in serial.tools.list_ports.comports()])

    @pyqtSlot(bytes)
    def send_command(self, payload: bytes):
        if not self.serial_port.is_open: return
        frame = protocol_handler.build_command_frame(payload, self.DEVICE_ADDRESS)
        self.serial_port.write(frame)
        if payload != b'caz' or not self.pd_read_timer.isActive():
            self.log_message(f"发送 -> {frame.hex(' ').upper()}", "blue")

    def connect_disconnect(self, checked):
        if not checked:
            if self.serial_port.is_open:
                self.pd_read_timer.stop(); self.auto_read_pd_checkbox.setChecked(False)
                # 1. 设置标志位，请求子线程退出循环
                self.serial_worker.stop()
                # 2. 从主线程关闭串口，这将导致子线程的 read() 操作立即出错并退出阻塞
                self.serial_port.close()
                # 3. 请求子线程的事件循环退出
                self.serial_thread.quit()
                # 4. 现在可以安全地等待子线程完全终止，不会再卡死
                self.serial_thread.wait()
                self.log_message("串口已断开", "black")
                self.connection_status_changed.emit(False)  # 发射“已断开”信号
            self.connect_button.setText("连接"); self.reset_ui_state()
        else:
            port = self.port_combobox.currentText()
            if not port: self.log_message("错误: 未选择串口！", "red"); self.connect_button.setChecked(False); return
            self.serial_port.port, self.serial_port.baudrate = port, 115200
            try:
                self.serial_port.open();
                self.log_message(f"已连接到 {port}", "green");
                self.connect_button.setText("断开")
                self.connection_status_changed.emit(True)  # 发射“已连接”信号
                self.serial_thread = QThread();
                self.serial_worker = SerialWorker(self.serial_port)
                self.serial_worker.moveToThread(self.serial_thread)
                self.serial_thread.started.connect(self.serial_worker.run)
                self.serial_worker.data_received.connect(self.handle_received_frame)
                self.serial_worker.error_occurred.connect(lambda msg: self.log_message(f"错误: {msg}", "red"))
                self.serial_worker.connection_lost.connect(self._on_connection_lost)
                self.serial_thread.start()
            except serial.SerialException as e:
                self.log_message(f"连接失败: {e}", ""); self.connect_button.setChecked(False)

    def log_message(self, message, color="black"):
        timestamp = time.strftime('%H:%M:%S')
        formatted_message = f'<span style="color: {color};">[{timestamp}] {message}</span>'
        self.log_text_edit.appendHtml(formatted_message)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyleSheet(GLOBAL_STYLE_SHEET)

    # --- 1. Create QWidget as the splash screen container ---
    splash = QWidget()
    # Increased size slightly to better fit the larger font
    splash.setFixedSize(700, 250)
    splash.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
    splash.setWindowFlags(Qt.WindowType.SplashScreen | Qt.WindowType.FramelessWindowHint)

    # --- 2. Create controls within the container ---
    # a. Text label
    text_label = QLabel(splash)
    text_label.setText("浙江大学光电学院\n微纳光纤温度传感系统")
    text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
    text_label.setGeometry(0, 0, splash.width(), splash.height())

    splash_font = QFont("楷体")
    splash_font.setPointSize(36)  # Increased font size
    splash_font.setBold(True)
    text_label.setFont(splash_font)
    text_label.setStyleSheet("color: black; background: transparent;")

    # b. Skip button
    skip_button = QPushButton("跳过 >>", splash)
    skip_button.setStyleSheet("""
        QPushButton {
            background-color: rgba(128, 128, 128, 0.8);
            color: white;
            border: 1px solid white;
            border-radius: 8px;
            padding: 5px 10px;
        }
        QPushButton:hover { background-color: rgba(150, 150, 150, 0.9); }
        QPushButton:pressed { background-color: rgba(100, 100, 100, 0.9); }
    """)
    skip_button.adjustSize()
    skip_button.move(splash.width() - skip_button.width() - 10, 10)

    # --- 3. Create and apply the combined mask ---
    mask_pixmap = QPixmap(splash.size())
    mask_pixmap.fill(Qt.GlobalColor.black)
    painter = QPainter(mask_pixmap)
    painter.setPen(Qt.GlobalColor.white)

    # Draw text part of the mask
    painter.setFont(splash_font)  # This now uses the new, larger font
    painter.drawText(text_label.rect(), Qt.AlignmentFlag.AlignCenter, text_label.text())

    # Draw button part of the mask
    painter.setBrush(Qt.GlobalColor.white)
    painter.setPen(Qt.GlobalColor.transparent)
    painter.drawRoundedRect(skip_button.geometry(), 8.0, 8.0)

    painter.end()
    splash.setMask(mask_pixmap.mask())

    # --- 4. Set up logic for closing the splash screen ---
    main_window = SerialProtocolApp()
    timer = QTimer()
    timer.setSingleShot(True)

    splash_state = [False]  # [is_closed]

    def show_main_window():
        if not splash_state[0]:
            splash_state[0] = True
            timer.stop()
            splash.close()
            main_window.show()


    # Connect signals
    timer.timeout.connect(show_main_window)
    skip_button.clicked.connect(show_main_window)

    # --- 5. Launch! ---
    splash.show()
    timer.start(5000)

    sys.exit(app.exec())